package com.xjnt.tvos.tvlibs.jsbridge;

import android.webkit.JavascriptInterface;
import android.webkit.WebView;

import com.xjnt.tvos.tvlibs.jsbridge.annotation.Callback;
import com.xjnt.tvos.tvlibs.jsbridge.annotation.ApiMethod;
import com.xjnt.tvos.tvlibs.jsbridge.data.DataBuilder;
import com.xjnt.tvos.tvlibs.jsbridge.data.ParamConsts;
import com.xjnt.tvos.tvlibs.jsbridge.data.ParamManager;
import com.xjnt.tvos.tvlibs.jsbridge.executor.NativeExecutor;
import com.xjnt.tvos.tvlibs.jsbridge.executor.ScriptExecutor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * 通讯数据构建器
 * Created by caoyong on 18-1-5.
 */
public class BridgeManager {

    private String jsNotify;

    private ScriptExecutor mScriptExecutor;
    private Map<String, NativeExecutor> mNativeExecutors = new HashMap<>();
    private Map<String, NativeExecutor> mNativeCallbacks = new HashMap<>();

    private int uniqueId = 0;

    public BridgeManager(WebView view, String jsNotify) {
        this.mScriptExecutor = new ScriptExecutor(view);
        this.jsNotify = jsNotify;
    }

    @JavascriptInterface
    public void invoke(String dataJSON) {
        final DataBuilder objData = DataBuilder.build(dataJSON);
        // JS 请求
        if (objData.isRequest()) {
            String methodName = objData.get(ParamConsts.KEY_METHOD_NAME, String.class);
            NativeExecutor executor = mNativeExecutors.get(methodName);
            if (executor != null) {
                objData.put(ParamConsts.KEY_CALLBACK_PROCESS, new CallProcess(this, objData));
                executor.invoke(objData);
            } else {
                String callbackId = objData.get(ParamConsts.KEY_CALLBACK_ID, String.class);
                objData.clear();
                String errMsg = "The invocation of a native application that does not provide a method for " + methodName;
                objData.setResponseStatus(0, errMsg);
                objData.put(ParamConsts.KEY_RESPONSE_ID, callbackId);
                sendToClient(objData.toDataString());
            }
        }
        // JS 响应
        else {
            String responseId = objData.get(ParamConsts.KEY_RESPONSE_ID, String.class);
            NativeExecutor executor = mNativeCallbacks.get(responseId);
            if (executor != null) {
                executor.invoke(objData);
                mNativeCallbacks.remove(responseId);
            }
        }
    }

    @SuppressWarnings({"unchecked"})
    public <T> T createJSCommand(Class<T> clazz) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String jsMethod = null;
                ApiMethod apiMethod = method.getAnnotation(ApiMethod.class);
                if (apiMethod != null) {
                    jsMethod = apiMethod.value();
                }
                DataBuilder dataBuilder = DataBuilder.build();
                dataBuilder.put(ParamConsts.KEY_METHOD_NAME, jsMethod);
                ParamManager paramManager = ParamManager.create(method);
                paramManager.setValues(dataBuilder, args);
                requestJS(dataBuilder);
                return new Object();
            }
        });
    }

    private void requestJS(DataBuilder dataBuilder) {
        IServerCallback serverCallback = dataBuilder.get(ParamConsts.KEY_CALLBACK, IServerCallback.class);
        if (serverCallback != null) {
            String callbackId = this.getUniqueId();
            Method[] methods = serverCallback.getClass().getDeclaredMethods();
            for (Method method: methods) {
                Callback callback = method.getAnnotation(Callback.class);
                if(callback != null) {
                    dataBuilder.put(ParamConsts.KEY_CALLBACK_ID, callbackId);
                    NativeExecutor executor = NativeExecutor.create(serverCallback, method);
                    mNativeCallbacks.put(callbackId, executor);
                }
            }
            dataBuilder.remove(ParamConsts.KEY_CALLBACK);
        }
        sendToClient(dataBuilder.toDataString());
    }

    void sendToClient(String data) {
        mScriptExecutor.setCallMethod(jsNotify);
        mScriptExecutor.invoke(data);
    }

    public void register(Object instance) {
        if (null != instance) {
            Class clazz = instance.getClass();
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method: methods) {
                ApiMethod apiMethod = method.getAnnotation(ApiMethod.class);
                if (apiMethod != null) {
                    NativeExecutor executor = NativeExecutor.create(instance, method);
                    this.mNativeExecutors.put(apiMethod.value(), executor);
                }
            }
        }
    }

    private String getUniqueId() {
        return ("S_" + uniqueId++) + System.currentTimeMillis();
    }
}
